/*
 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Autogenerated file -- DO NOT EDIT!

#ifndef PANDA_EVENTS_GEN_H
#define PANDA_EVENTS_GEN_H

#include "macros.h"
#include "os/mutex.h"
#include "utils/list.h"
#include "runtime/include/mem/panda_string.h"
#include <fstream>
#include <variant>
#include <vector>

namespace ark {

namespace events {

% EventsData.events.each do |event|
%   event.fields.select(&:is_enum?).each do |field|
enum class <%= field.type %> {
    <%= field.enums.map(&:upcase).join(', ') %>
};
%   end
% end

class EventsBase {
public:
    EventsBase() = default;
    virtual ~EventsBase() = default;
    NO_COPY_SEMANTIC(EventsBase);
    NO_MOVE_SEMANTIC(EventsBase);

% EventsData.events.each do |event|
    virtual void <%= event.name.camelize %>(<%= event.fields.map {|f| f.arg_type}.join(', ') %>) {}  // NOLINT
% end

    auto& GetLock() {
        return lock_;
    }
private:
    os::memory::Mutex lock_;
};

% EventsData.events.each do |event|
%   event.fields.select(&:is_enum?).each do |field|
inline std::ostream& operator<<(std::ostream& os, <%= field.type %> field) {
    switch (field) {
% field.enums.each do |enum|
        case <%= field.type %>::<%= enum.upcase %>: os << "<%= enum.upcase %>"; break;
% end
    }
    return os;
}
%   end
% end

class EventsMemory : public events::EventsBase {

public:
% EventsData.events.each do |event|
    struct <%= event.name.camelize %>Event{
%   event.fields.each do |field|
        <%= field.type %> <%= field.name.camelizeback %>;
%   end
    };
% end

    // NOLINTNEXTLINE(fuchsia-multiple-inheritance)
    struct EventRecord : public ListNode, public std::variant<<%= EventsData.events.map{|e| e.name.camelize + 'Event'}.join(', ') %>> {
        using VariantBase = std::variant<<%= EventsData.events.map{|e| e.name.camelize + 'Event'}.join(', ') %>>;
% EventsData.events.each do |event|
        explicit EventRecord(<%= event.name.camelize %>Event v) : VariantBase(v) {} // NOLINT(bugprone-throw-keyword-missing)
% end
    };

public:
    EventsMemory() = default;
    ~EventsMemory() override {
        os::memory::LockHolder lock(GetLock());
        for (auto it = eventList_.begin(); it != eventList_.end(); ) {
            delete &*(it++);
        }
    }
    NO_COPY_SEMANTIC(EventsMemory);
    NO_MOVE_SEMANTIC(EventsMemory);

% EventsData.events.each do |event|
    void <%= event.name.camelize %>(<%= event.fields.map {|f| f.arg_type + ' ' + f.name.camelizeback }.join(', ') %>) override {
        os::memory::LockHolder lock(GetLock());
        auto event = new EventRecord(<%= event.name.camelize %>Event{<%= event.fields.map {|f| f.name.camelizeback }.join(', ') %>});
        last_ = eventList_.InsertAfter(last_, *event);
    }
% end

    template<typename EventT, typename Func>
    void EnumerateEvents(Func func) {
        os::memory::LockHolder lock(GetLock());
        for (const auto& event : eventList_) {
            if(auto p = std::get_if<EventT>(&event)) {
                func(static_cast<const EventT&>(*p));
            }
        }
    }

    template<typename EventT, typename Comp>
    bool Find(Comp cmp) {
        bool found = false;
        EnumerateEvents<EventT>([&found, &cmp](const auto& event) {
          if (cmp(event)) {
              found = true;
              return false;
          }
          return true;
        });
        return found;
    }

    template<typename EventT, typename F = bool>
    std::vector<const EventT*> Select([[maybe_unused]] F pred = false) {
        std::vector<const EventT*> res;
        if constexpr (std::is_same_v<F, bool>) {
            EnumerateEvents<EventT>([&res](const auto& event) { res.push_back(&event); });
        } else {
            EnumerateEvents<EventT>([&pred, &res](const auto& event) {
              if (pred(&event)) {
                  res.push_back(&event);
              }
            });
        }
        return res;
    }

private:
    List<EventRecord> eventList_ GUARDED_BY(GetLock());
    List<EventRecord>::Iterator last_{eventList_.before_begin()};
};

class EventsCsv : public events::EventsBase {
public:
    EventsCsv() {
        file_.open("events.csv");
    }
    explicit EventsCsv(std::string_view path) {
        file_.open(path.data());
    }
    ~EventsCsv() override {
        os::memory::LockHolder lock(GetLock());
        file_.close();
    }
    NO_COPY_SEMANTIC(EventsCsv);
    NO_MOVE_SEMANTIC(EventsCsv);

% EventsData.events.each do |event|
    void <%= event.name.camelize %>(<%= event.fields.map {|f| f.arg_type + ' ' + f.name.camelizeback }.join(', ') %>) override {
        os::memory::LockHolder lock(GetLock());
        file_ << "<%= event.name.camelize %>," << <%= event.fields.map{|f| f.name.camelizeback }.join(" << ',' << ") %> << std::endl;
    }
% end

private:
    std::ofstream file_ GUARDED_BY(GetLock());
};

class EventsLog : public events::EventsBase {
public:
    EventsLog() {
        Logger::EnableComponent(Logger::EVENTS);
    }
    ~EventsLog() override {
        Logger::DisableComponent(Logger::EVENTS);
    }
    NO_COPY_SEMANTIC(EventsLog);
    NO_MOVE_SEMANTIC(EventsLog);

% EventsData.events.each do |event|
    void <%= event.name.camelize %>(<%= event.fields.map {|f| f.arg_type + ' ' + f.name.camelizeback }.join(', ') %>) override {
        LOG(INFO, EVENTS) << "<%= event.name.camelize %>," << <%= event.fields.map {|f| f.name.camelizeback }.join(" << ',' << ") %>;
    }
% end
};

}  // namespace events

class PANDA_PUBLIC_API Events {
public:
    enum StreamKind { CSV, MEMORY, LOG };

    template<StreamKind KIND, typename... Args>
    static void Create(Args&&... args) {
        if constexpr (KIND == StreamKind::CSV) {  // NOLINT
            pevents_ = new events::EventsCsv(std::forward<Args>(args)...);
        } else if constexpr (KIND == StreamKind::MEMORY) {  // NOLINT
            pevents_ = new events::EventsMemory;
        } else if constexpr (KIND == StreamKind::LOG) {  // NOLINT
            pevents_ = new events::EventsLog;
        } else {  // NOLINT
            UNREACHABLE();
        }
    }

    template<typename... Args>
    static void Create(std::string_view output, Args&&... args) {
        if (output == "csv") {
            pevents_ = new events::EventsCsv(std::forward<Args>(args)...);
        } else if (output == "memory") {
            pevents_ = new events::EventsMemory;
        } else if (output == "log") {
            pevents_ = new events::EventsLog;
        } else {  // NOLINT
            UNREACHABLE();
        }
    }

    static void Destroy() {
        delete pevents_;
        pevents_ = nullptr;
    }

    template<StreamKind KIND>
    static auto CastTo() {
        if constexpr (KIND == StreamKind::CSV) {  // NOLINT
            return static_cast<events::EventsCsv*>(pevents_);
        } else if constexpr (KIND == StreamKind::MEMORY) {  // NOLINT
            return static_cast<events::EventsMemory*>(pevents_);
        } else {  // NOLINT
            UNREACHABLE();
        }
    }

    static bool IsEnabled() {
        return pevents_ != nullptr;
    }

    static events::EventsBase* GetEvents() {
        return pevents_;
    }

private:
    static inline events::EventsBase* pevents_{nullptr};
};

#ifndef NDEBUG

#define PANDA_EVENTS_ENABLED

% EventsData.events.each do |event|
%   if !event.enable?
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EVENT_<%= event.name.upcase %>_ENABLED 0
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EVENT_<%= event.name.upcase %>(<%= event.fields.map(&:name).join(', ') %>)
%   else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EVENT_<%= event.name.upcase %>_ENABLED 1
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EVENT_<%= event.name.upcase %>(<%= event.fields.map(&:name).join(', ') %>)      \
    if (Events::IsEnabled()) {                                                          \
        Events::GetEvents()-><%= event.name.camelize %>(<%= event.fields.map(&:name).join(', ') %>); \
    }
%   end
% end
#else
% EventsData.events.each do |event|
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define EVENT_<%= event.name.upcase %>(<%= event.fields.map(&:name).join(', ') %>)
% end
#endif

}  // namespace ark

#endif  // PANDA_EVENTS_GEN_H
